pub fn learn_oop() {
  //_learn_trait_obj0();
  _learn_oop_statep_0();
}

// 使用trait对象模拟继承
// 顾及不同类型值的trait对象
// 定义通用行为的trait
pub trait Draw {
  fn draw(&self);
}

// 与定义使用了带有trait bound的泛型类型参数的结构体不同.泛型类型参数一次
// 只能替代一个具体类型,而trait对象则允许在运行时替代多种具体类型.
// 使用trait对象,一个Screen实例可以存放一个即能包含Box<Button>,也能包含
// Box<TextField>的Vec<T>.
pub struct Screen {
  pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
  pub fn run(&self) {
    for component in self.components.iter() {
      component.draw();
    }
  }
}

// 泛型类型参数一次只能替代一个具体类型
// 这限制了ScreenGen实例必须拥有一个全是Button类型或者全是TextField类型
// 的组件列表.
// 如果只需要同质(相同类型)集合,则倾向于使用泛型和trait bound,因为其定义会
// 在编译时采用具体类型进行单态化
pub struct ScreenGen<T: Draw> {
  pub components: Vec<T>,
}
impl<T> ScreenGen<T>
where
  T: Draw,
{
  pub fn run(&self) {
    for component in self.components.iter() {
      component.draw();
    }
  }
}

// 实现trait
pub struct Button {
  pub width: u32,
  pub height: u32,
  pub label: String,
}
impl Draw for Button {
  fn draw(&self) {
    println!("button: {}, {}, {}", self.label, self.width, self.height);
  }
}

struct SelectBox {
  width: u32,
  height: u32,
  options: Vec<String>,
}

impl Draw for SelectBox {
  fn draw(&self) {
    println!(
      "selectBox: {}, {}, {:?}",
      self.width, self.height, self.options
    );
  }
}

fn _learn_trait_obj0() {
  let screen = Screen {
    components: vec![
      Box::new(SelectBox {
        width: 75,
        height: 10,
        options: vec![
          String::from("Yes"),
          String::from("Maybe"),
          String::from("No"),
        ],
      }),
      Box::new(Button {
        width: 50,
        height: 10,
        label: String::from("OK"),
      }),
      // 编译错误:the trait bound `String: Draw` is not satisfied the following other types implement trait `Draw`:
      // Button SelectBox required for the cast from `Box<String>` to `Box<dyn Draw>
      // Box::new(String::from("hello")),
    ],
  };
  screen.run();
}
// 单态化处理:编译器为每一个被泛型类型参数代替的具体类型生成了函数和方法的非泛型实现.单态化产生的代码
// 执行静态分发(static dispatch). 静态分发发生于编译器在编译时就知晓调用了什么分发的时候.这与动态
// 分发(dynamic dispatch)相对, 这时编译器在编译时无法知晓调用了什么方法.在动态分发的场景下,编译器
// 会生成负责在运行时确定该调用什么分发的代码.

// 当使用trait对象时,rust必须使用动态分发.编译器无法知晓所有可能用于trait对象代码的类型,所以它也不知道
// 应该调用哪个类型的哪个方法实现. 为此, rust在运行时使用trait对象中的指针来知晓需要调用哪个分发. 动态
// 分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化.

// 面向对象设计模式的实现
// 状态模式(state pattern)

pub struct Post {
  // 为何放在Option中?
  // 使用Box是为了trait对象,即通过动态分发模拟oop的多态
  state: Option<Box<dyn State>>,
  content: String,
}

impl Post {
  pub fn new() -> Post {
    Post {
      // Draft是博文的初始状态
      state: Some(Box::new(Draft {})),
      // 初始空字符串
      content: String::new(),
    }
  }
  // 获取一个self的可变引用,因为需要改变调用add_text的Post实例
  pub fn add_text(&mut self, text: &str) {
    self.content.push_str(text);
  }
  pub fn content(&self) -> &str {
    // 调用Option的as_ref方法是因为需要Option中值的引用而不是获取其所有权.
    // 因为state是一个Option<Box<dyn State>>,调用as_ref会返回一个Option<&Box<dyn State>>.
    // 如果不调用as_ref,将会得到一个错误,因为不能将state移动出借用的&self函数参数.
    // unwrap之后,得到&Box<dyn State>,再调用content方法,Deref强制转换会作用于
    // &和Box,这样最终会调用实现了State trait类型的content方法.
    self.state.as_ref().unwrap().content(self)
    // 同样的,如果不用as_ref而直接调用unwrap,会尝试消费掉state,但结构体中的字段不能为空,所以编译报错
    // self.state.unwrap().content(self)
  }
  pub fn request_review(&mut self) {
    if let Some(s) = self.state.take() {
      // 第二个request_review方法会消费当前的状态并返回一个新状态
      self.state = Some(s.request_review())
    }
  }
  pub fn approve(&mut self) {
    if let Some(s) = self.state.take() {
      self.state = Some(s.approve())
    }
  }
}

trait State {
  // 不同于使用self,&self或者&mut self作为方法的第一个参数,这里使用了self:Box<Self>.
  // 这个语法意味着该方法只可在持有这个类型的Box上被调用.这个语法获取了Box<Self>的所有权使老状态
  // 无效化,以便Post的状态值可以转换为一个新状态
  // 为了消费老状态,request_review方法需要获取状态值的所有权.这就是Post的state字段中Option的来历:
  // 调用take方法将state字段中的Some值取出并留下一个None,因为Rust不允许结构体实例中存在值为空的字段.
  // 这使得我们将state的值移出Post而不是借用它.接着我们将博文的state值设置为这个操作的结果.
  fn request_review(self: Box<Self>) -> Box<dyn State>;

  fn approve(self: Box<Self>) -> Box<dyn State>;

  // 对于Draft和PendingReview状态,都不返回content,只有发表了的post才会有content
  fn content<'a>(&self, post: &'a Post) -> &'a str {
    // 返回的是post的一部分,因此其生命周期与post相关,而默认的消除规则是与self相关
    // 就需要覆盖默认的生命周期
    ""
  }
}

struct Draft {}

impl State for Draft {
  fn request_review(self: Box<Self>) -> Box<dyn State> {
    Box::new(PendingReview {})
  }
  fn approve(self: Box<Self>) -> Box<dyn State> {
    self
  }
}
struct PendingReview {}
impl State for PendingReview {
  fn request_review(self: Box<Self>) -> Box<dyn State> {
    self
  }
  fn approve(self: Box<Self>) -> Box<dyn State> {
    Box::new(Published {})
  }
}

struct Published {}
impl State for Published {
  fn request_review(self: Box<Self>) -> Box<dyn State> {
    self
  }
  fn approve(self: Box<Self>) -> Box<dyn State> {
    self
  }
  fn content<'a>(&self, post: &'a Post) -> &'a str {
    &post.content
  }
}

fn _learn_oop_statep_0() {
  let mut post = Post::new();
  post.add_text("I ate a salad for lunch today");
  assert_eq!("", post.content());

  post.request_review();
  assert_eq!("", post.content());

  post.approve();
  assert_eq!("I ate a salad for lunch today", post.content());
}

// 状态模式的权衡取舍
// 完全按照面向对象语言的定义实现这个模式并没有尽可能地利用rust的优势.
// 让我们看看一些代码中可以做出的修改,来将无效的状态和状态转移变为编译时错误.

// 将状态和行为编码为类型
// 我们将展示如何稍微反思状态模式来进行一系列不同的权衡取舍.不同于完全封装状态和状态转移
// 使得外部代码对其毫不知情,我们将状态编码进不同的类型.如此,rust的类型检查就会将任何在只能
// 使用发布博文的地方使用草案博文的尝试变为编译错误.

mod rusty {
  fn _learn_oop_statep_1(){
    // 创建一个草案
    let mut post = Post::new();
    // 添加博文的内容
    post.add_text("I ate a salad for lunch today");
    // 不同于存在一个获取草案博文时返回空字符串的content方法,我们将使草案博文完全没有content方法.
    // 这样如果尝试获取草案博文的内容,将会得到一个方法不存在的编译错误.这使得我们不可能在生产环境
    // 以外显示出草案博文的内容,因为这样的代码甚至就不能编译
    let post = post.request_review();
    let post = post.approve();
    assert_eq!("", post.content())
  }

  pub struct Post {
    content: String,
  }
  pub struct DraftPost{
    content: String,
  }
  // 带有content方法的Post和没有content方法的DraftPost
  // Post和DraftPost结构体都有一个私有的content字段来储存博文的文本.这些结构体不再
  // 有state字段,因为我们将状态编码改为结构体类型.Post将代表发布的博文,它有一个返回
  // content的 content方法
  // 仍有一个Post::new函数,不过不同于返回Post实例,它返回DraftPost实例.现在不可能
  // 创建一个Post实例,因为content是私有的,同时没有任何函数返回Post.
  impl Post {
    pub fn new()->DraftPost{
      DraftPost { content: String::new() }
    }
    pub fn content(&self)->&str{
      &self.content
    }
  }
  // DraftPost上定义了一个add_text方法,这样就可以像之前那样向content增加文本,不过注意
  // DraftPost并没有定义content方法!如此现在程序确保了所有博文都从草案开始,同时草案博文
  // 没有任何可供展示的内容.任何绕过这些限制的尝试都会产生编译错误.
  impl DraftPost{
    pub fn add_text(&mut self, text:&str){
      self.content.push_str(text);
    }
    pub fn request_review(self)->PendingReviewPost{
      PendingReviewPost{
        content:self.content,
      }
    }
  }

  pub struct PendingReviewPost{
    content:String,
  }
  impl PendingReviewPost{
    pub fn approve(self)->Post{
      Post { content: self.content }
    }
  }

  // 实现状态转移为不同类型的转换
  // 那么如何得到发布的博文呢?我们希望强制执行的规则是草案博文在可以发布之前必须被审核通过.
  // 等待审核状态的博文仍然不会显示任何内容.让我们通过增加另一个结构体 PendingReviewPost
  // 来实现这个限制,在DraftPost上定义request_review方法来返回PendingReviewPost,并在
  // PendingReviewPost上定义approve方法来返回Post

  // request_review和approve方法获取self的所有权,因此会消费DraftPost和PendingReveiwPost
  // 实例,并分别转换为PendingReviewPost和发布的Post.这样在调用request_review之后
  // 就不会遗留任何DraftPost实例,后者同理. PendingReviewPost并没有定义content方法,所以
  // 尝试读取其内容会导致编译错误,DraftPost同理. 因为得到唯一定义了content方法的Post实例
  // 的途径是调用PendingReviewPost的approve方法. 而得到PendingReviewPost的唯一方法
  // 是调用DraftPost的request_review方法,现在我们就将发博文的工作流编码进了类型系统.

  // 需要对 _learn_oop_statep_1 函数做一些小的修改.因为request_review和approve返回新
  // 实例而不是修改被调用的结构体,所以我们需要增加更多的let post=覆盖赋值来保存返回的实例.
  // 也不再能断言草案和等待审核的博文内容为空字符串了,我们也不再需要它们:不能编译尝试使用
  // 这些状态下博文内容的代码

  // 修改后,这个新的实现不再完全遵守面向对象的状态模式:状态间的转换不再完全封装在Post实现中.
  // 然而,得益于类型系统和编译时类型检查,我们得到的是 运行时不可能出现无效状态. 这确保了
  // 某些特定的bug,比如显示未发布博文的内容,将在部署到生产环境之前被发现.

  // 即使rust能够实现面向对象设计模式,也有其他像将状态编码进类型这样的模式存在.这些模式有着
  // 不同的权衡取舍.虽然你可能非常熟悉面向对象模式,重新思考这些问题来利用rust提供的像在编译时
  // 避免一些bug这样有益的功能. 在rust中,面向对象并不总是最好的解决方案,因为rust拥有像
  // 所有权这样的面向对象语言没有的功能.
}